/*
This software is distributed under the Simplified BSD License:

Copyright (c) 2008, Chris Venter <chris.venter@gmail.com>
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, 
    	this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, 
    	this list of conditions and the following disclaimer in the documentation 
    	and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 
DAMAGE.
*/
#ifndef GENERIK_READCSV_H
#define GENERIK_READCSV_H

/** \file Declares a CSV parser for smaller-sized CSV files

\note Caution: this code is in beta, and may be removed in future.
It all depends on how useful I find CSV files to be...
*/

#include <iostream>
#include <fstream>
#include <vector>
#include <list>

#include "exceptions.h"

#include "tracing.h"

/*
#define GENERIK_STRIP_CR(LINE) \
	if (LINE[LINE.size()-1]=='\r') LINE[LINE.size()-1] = 0
*/

namespace generik
{
	/** strips the trailing CR from a string.
	 In practice, this basically converts a line from an errant MSDOS file
	 to a plain line of text.
	 It ignores lines where the trailing character is not a CR, so correctly
	 handles lines from good files.
	 */
	inline void stripcr(std::string& line)
	{
		if (line[line.size()-1] == '\r') line.erase(line.size()-1);
	}

	/** strips the trailing CR from a string.
	 This is the const version of stripcr.
	 */
	inline std::string stripcr(const std::string& line)
	{
		if (line[line.size()-1] == '\r')
		{
			std::string line_ = line;
			return line_.erase(line_.size()-1);
		}
		return line;
	}

	inline std::vector<char*> parsecsv(std::string& line, char sep = ',')
	{
		stripcr(line);

		// starts stores the location of any items in the line
		// if the line is empty, starts is empty.
		// starts is used to construct the char** array
		std::vector<char*> starts;

		size_t start = 0;
		size_t stop = line.find_first_of(',', start);
		//GENERIK_TRACE_VALUE(line.substr(start, stop-start));
		//starts.push_back(start);
		starts.push_back(&line[start]);
		while (stop != std::string::npos)
		{
			line[stop] = 0;
			start = stop + 1;
			stop = line.find_first_of(',', start);
			//GENERIK_TRACE_VALUE(line.substr(start, stop-start));
			//starts.push_back(start);
			starts.push_back(&line[start]);
		}

		return starts;
	}

	/** This is a callback-based parser for smaller-sized CSV files
	\param filename The CSV file to be parsed
	\param callback A callback to a function which accepts a line from the CSV file
	\param udata A general pointer which is also passed to the callback function, defaults to NULL
	\param sep The seperator which is used by the CSV file (defaults to ','
	\param discard1st A flag which tells the parser to discard the first line, defaults to false

	If @discard1st is true, the first line of the CSV file is skipped.
	This is useful if the first line is a header line (to allow humans to understand the columns in the CSV file).

	The parser throws a std::runtime_error if the file could not be opened
	*/
	inline void readcsv(const std::string& filename, void(*callback)(int,char**,void*), void* udata = 0, char sep = ',', bool discard1st = false)
	{
		// open the file and check whether it is open
		std::ifstream file(filename.c_str());
		if (!file.is_open())
		{
			std::string error = "could not open file [" + filename + "]";
			throw std::runtime_error(error);
		}
		
		if (discard1st)
		{
			std::string line;
			std::getline(file, line);
		}
		
		while (!file.eof())
		{
			std::string line;
			std::getline(file, line);
			
			if (!line.empty())
			{
				std::vector<char*> values = parsecsv(line, sep);
				(*callback)(values.size(), &values[0], udata);
			}
			else
			{
				(*callback)(0, 0, udata);
			}
		}
	}

	/** A templated parser for smaller-sized CSV files.
	\param TYPE The type of struct which can be created using a line of data in the CSV file
	\param filename The CSV file which is to be read
	\param sep The separator used by the CSV file
	\param discard1st Whether or not the first line should be discarded
	\returns A list of TYPE instances which correspond to the parsed data in the CSV file
	*/
	template <typename TYPE>
	std::list<TYPE> readcsv(const std::string& filename, char sep = ',', bool discard1st = false)
	{
		std::list<TYPE> results;
		
		struct handler
		{
			std::list<TYPE>& results;
			handler(std::list<TYPE>& r) : results(r) {}
			static void callback(int c, char** v, void* object)
			{
				handler* h = (handler*)object;
				h->callback(c, v);
			}
			void callback(int c, char** v)
			{
				try
				{
					// Create an instance of TYPE, passing it the parameters c and v.
					// if the constructor throws a runtime_check_exception, we can
					// (reasonably) safely assume that the c and v parameters were insufficient
					// to create the instance, so we (in effect) skip this line of data
					results.push_back(TYPE(c, v));
				}
				catch(runtime_check_exception&)
				{}
			}
		};
		
		handler h(results);
		readcsv(filename, handler::callback, &h, sep, discard1st);
		
		return results;
	}
	
}

#endif

